package com.dcivision.framework.xml;

// For write operation
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.impl.LogFactoryImpl;
import org.w3c.dom.Attr;
import org.w3c.dom.CDATASection;
import org.w3c.dom.Comment;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.ProcessingInstruction;
import org.w3c.dom.Text;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.DTDHandler;
import org.xml.sax.EntityResolver;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;

public class XMLUtility implements XMLReader {

  public static final String REVISION = "$Revision: 1.11.4.3 $";

  protected Log log = new LogFactoryImpl().getInstance(this.getClass());

  ContentHandler handler;
  OutputStream out = System.out;
  Document doc;
  InputSource inputSource;

  public XMLUtility() {}

  public XMLUtility( String indexFile) {     //indexFile = fullpath of xml file
    try {
      inputSource = new InputSource(indexFile);
      DocumentBuilderFactory dfactory = DocumentBuilderFactory.newInstance();
      DocumentBuilder docBuilder = dfactory.newDocumentBuilder();
      doc = docBuilder.parse(inputSource);
    } catch (ParserConfigurationException pce) {
      log.error(pce, pce);
    } catch (SAXException se) {
      log.error(se, se);
    } catch (IOException ioe) {
      log.error(ioe, ioe);
    }
  }
  
  /**
   * EIP-2211
   * to be used when the content is being made available from a .war archive
   * @param xmlInputStream
   */
  public XMLUtility(InputStream xmlInputStream) {
    try {
      DocumentBuilderFactory dfactory = DocumentBuilderFactory.newInstance();
      DocumentBuilder docBuilder = dfactory.newDocumentBuilder();
      doc = docBuilder.parse(xmlInputStream);      
    } catch (ParserConfigurationException pce) {
      log.error(pce, pce);
    } catch (SAXException se) {
      log.error(se, se);
    } catch (IOException ioe) {
      log.error(ioe, ioe);
    }
  }

  // no namespaces required
  String nsu = "";  // NamespaceURI
  String indent = ""; // for readability!

  public String getIndent() {
    return this.indent;
  }

  public void setIndent(String indent) {
    this.indent = indent;
  }

  public void setOutputStream(OutputStream out) {
    this.out = out;
  }

  public void writeTag(String tagName, String value, Attributes atts) throws SAXException {
    if (value==null) {
      value=""; // value is null will cause nullpoint exception (lee.lv 2006/10/25 EIP-603) 
    }
    handler.ignorableWhitespace(indent.toCharArray(),
                                0, // start index
                                indent.length()
                                );
    handler.startElement(nsu, tagName, tagName /*"qName"*/, atts);

    handler.characters(value.toCharArray(),
                       0,
                       value.length());
    handler.endElement(nsu, tagName, tagName);
  }

  public void writeStartTag(String tagName, String value, Attributes atts) throws SAXException {
    handler.ignorableWhitespace(indent.toCharArray(),
                                0, // start index
                                indent.length()
                                );

    handler.startElement(nsu, tagName, tagName, atts);
  }

  public void writeEndTag(String tagName) throws SAXException {
    handler.ignorableWhitespace(indent.toCharArray(),
                                0, // start index
                                indent.length()
                                );
    handler.endElement(nsu, tagName, tagName);
  }

  /**
   * changeOrAddAttr
   *
   * To add a new attribute
   * Or to change the value of the an orignally existed attribute
   * It depends on the attrName pass in
   *
   * @param attrName      The orginal or new attribute name
   * @param attrValue     New value of the attribute
   *
   */
  public void changeOrAddAttr(String attrName, String attrValue) {
    doc.getDocumentElement().setAttribute(attrName, attrValue);
  }

  /**
   * replaceNode
   *
   * To replace the orginal node by a new node
   * Or just to change the content an originally existed node
   * If just to change the content, oldNodeName=newNodeName
   *
   * @param oldNodeName      The orginal node name
   * @param newNodeName      The new node name
   * @param textNode1        The content of the node
   *
   */
  public void replaceNode(String oldNodeName, String newNodeName, String textNode) {
    NodeList ls = doc.getElementsByTagName(oldNodeName);
    if (ls != null && ls.getLength() > 0) {
      Element oldChild = (Element) ls.item(0);
      Element newChild = doc.createElement(newNodeName);
      // Add a text node to the element
      newChild.appendChild(doc.createTextNode(textNode));
      oldChild.getParentNode().replaceChild(newChild, oldChild);
    } else {
      // Node with the tag name can't be found
      log.error("replace node: node name cannot be found, node name = " + oldNodeName);
    }
  }

  /**
   * duplicateNode
   *
   * To duplicate a node and insert it immediately after the cloned element
   *
   * @param DupNodeName      The name of the node to be duplicated
   *
   */
  public void duplicateNode(String DupNodeName) {
    NodeList ls = doc.getElementsByTagName(DupNodeName);
    if (ls != null && ls.getLength() > 0) {
      Element element = (Element) ls.item(0);
      // Make a copy of the element, including any child nodes
      Element dup = (Element) element.cloneNode(true);
      // Insert the copy immediately after the cloned element
      element.getParentNode().insertBefore(dup, element.getNextSibling());
    } else {
      // Node with the tag name can't be found
      log.error("duplicate node: node name cannot be found, node name = " +
                DupNodeName);
    }
  }

  /**
   * insertNewNode
   *
   * To insert a sibling node
   *
   * @param siblingNodeName  The sibling node name
   * @param newNodeName      The new node name
   * @param textNode         The content of the new node
   *
   */
  public void insertNewNode(String siblingNodeName, String newNodeName, String textNode) {
    NodeList ls = doc.getElementsByTagName(siblingNodeName);
    if (ls!=null && ls.getLength()>0) {
      Element siblingNode = (Element) ls.item(0);
      Element insertNode = doc.createElement(newNodeName);
      // Add a text node to the element
      insertNode.appendChild(doc.createTextNode(textNode));
      // Insert the copy immediately after the cloned element
      siblingNode.getParentNode().insertBefore(insertNode, siblingNode.getNextSibling());
    } else {
      // Node with the tag name can't be found
      log.error("insert new node: node name cannot be found, node name = "+siblingNodeName);
    }
  }

  /**
   * addCommentBefore
   *
   * Adding a comment before a DOM element
   *
   * @param nodeName         The sibling node name
   * @param newComment       The comment to be added
   *
   */
  public void addCommentBefore(String nodeName, String newComment) {
    NodeList ls = doc.getElementsByTagName(nodeName);
    if (ls!=null && ls.getLength()>0) {
      for (int i = 0; i < ls.getLength(); i++) {
        Element element = (Element) ls.item(i);
        Comment comment = doc.createComment(newComment);
        // Add the comment after this element
        element.getParentNode().insertBefore(comment, element);
      }
    } else {
      // Node with the tag name can't be found
      log.error("addCommentBefore: node name cannot be found, node name = "+nodeName);
    }
  }

  /**
   * addCommentBehind
   *
   * Adding a comment after a DOM element
   *
   * @param nodeName         The sibling node name
   * @param newComment       The comment to be added
   *
   */
  public void addCommentBehind(String nodeName, String newComment) {
    NodeList ls = doc.getElementsByTagName(nodeName);
    if (ls != null && ls.getLength() > 0) {
      for (int i = 0; i < ls.getLength(); i++) {
        Element element = (Element) ls.item(i);
        Comment comment = doc.createComment(i + " " + newComment);
        // Add the comment after this element
        element.getParentNode().insertBefore(comment, element.getNextSibling());
      }
    } else {
      // Node with the tag name can't be found
      log.error("addCommentBehind: node name cannot be found, node name = " +
                nodeName);
    }
  }

  /**
   * setOrAddDOMAttr
   *
   * To add a new attribute in a DOM Element
   * Or to change the value of the an attribute in a DOM Element
   * It depends on the attrName pass in
   *
   * @param nodeName      The name of the node
   * @param attrName      The orginal or new attribute name
   * @param attrValue     New value of the attribute
   *
   */
  public void setOrAddDOMAttr(String nodeName, String attrName, String attrValue) {
    NodeList ls = doc.getElementsByTagName(nodeName);
    if (ls != null && ls.getLength() > 0) {
      for (int i = 0; i < ls.getLength(); i++) {
        Element element = (Element) ls.item(i);
        // Change the value of an attribute
        element.setAttribute(attrName, attrValue);
      }
    } else {
      // Node with the tag name can't be found
      log.error("setOrAddDOMAttr: node name cannot be found, node name = " + nodeName);
    }
  }

  /**
   * getNodeListByTagName
   *
   * @param tagName
   * @return
   */
  public NodeList getNodeListByTagName(String tagName) {
    NodeList ls = doc.getElementsByTagName(tagName);
    return(ls);
  }

  /**
   * listDOMAttr
   *
   * To list all the attributes of a DOM Element
   *
   * @param nodeName      The name of the node wants to displace
   *
   */
  public void listDOMAttr(String nodeName) {
    NodeList ls = doc.getElementsByTagName(nodeName);
    if (ls != null && ls.getLength() > 0) {
      for (int i = 0; i < ls.getLength(); i++) {
        Element element = (Element) ls.item(i);
        // Get all the attributes of an element in a map
        NamedNodeMap attrs = element.getAttributes();
        // Process each attribute

        try {
          for (int j = 0; j < attrs.getLength(); j++) {
            Attr attri = (Attr) attrs.item(j);
            // Get attribute name and value
            String attrName = attri.getNodeName();
            String attrValue = attri.getNodeValue();
            out.write( (attrName + "=" + attrValue).getBytes());
            log.debug(attrName + " " + attrValue);
          }
        } catch (IOException ioe) {
          log.error("listDOMAttr IOException: " + ioe);
        }
      }
    } else {
      // Node with the tag name can't be found
      log.error("listDOMAttr: node name cannot be found, node name = " + nodeName);
    }
  }

  /**
   * splitTextNode
   *
   * Splitting a Text Node in a DOM by inserting a new element
   *
   * @param nodeName      The name of the node wants to be splitted
   * @param position      The text position to be splitted
   *
   */
  public void splitTextNode(String nodeName, String position) {
    NodeList ls = doc.getElementsByTagName(nodeName);
    if (ls != null && ls.getLength() > 0) {
      Element element = (Element) ls.item(0);
      // Get the text node
      Text text1 = (Text) element.getFirstChild();
      String string = text1.getData();
      // Split the node at the beginning of the position
      Text text2 = text1.splitText(string.indexOf(position));
      // Split the new text node at the end of the position
      Text text3 = text2.splitText(position.length());

      // Create a new element and move the middle text node to it
      Element newElement = doc.createElement("o");
      newElement.appendChild(text2);
      // Insert the new element where the middle node used to be
      element.insertBefore(newElement, text3);
    } else {
      // Node with the tag name can't be found
      log.error("splitTextNode: node name cannot be found, node name = " +
                nodeName);
    }
  }

  /**
   * mergeTextNode
   *
   * Merging Text Node in a DOM by removing an element
   *
   * @param nodeName      The name of the node to be removed
   *
   */
  public void mergeTextNode(String nodeName) {
    // Obtain the root element to remove
    NodeList ls = doc.getElementsByTagName(nodeName);
    if (ls!=null && ls.getLength()>0) {
      Element element = (Element)ls.item(0);

      // Get the parent of the element
      Node parent = element.getParentNode();

      // Move all children of the element in front of the element
      while (element.hasChildNodes()) {
        parent.insertBefore(element.getFirstChild(), element);
      }

      // Remove the element
      parent.removeChild(element);

      // Merge all text nodes under the parent
      parent.normalize();
    } else {
      // Node with the tag name can't be found
      log.error("mergeTextNode: node name cannot be found, node name = "+nodeName);
    }
  }

  /**
   * removeOneNode
   * Remove a specified node only
   * @param nodeName      The name of the node to be removed
   */
  public void removeOneNode(String nodeName) {
    NodeList ls = doc.getElementsByTagName(nodeName);
    if (ls!=null && ls.getLength()>0) {
      Element element = (Element)ls.item(0);
      element.getParentNode().removeChild(element);
    } else {
      // Node with the tag name can't be found
      log.error("removeOneNode: node name cannot be found, node name = "+nodeName);
    }
  }

  /**
   * removeALL
   * Remove a specified node including all of its child and comment
   * @param node          The document
   * @param nodeType      The type of the node
   * @param nodeName      The name of the node to be removed
   */
  public static void removeAll(Node node, short nodeType, String nodeName) {
    if (node.getNodeType() == nodeType && (nodeName == null || node.getNodeName().equals(nodeName))) {
      node.getParentNode().removeChild(node);
    } else {
      // Visit the children
      NodeList ls = node.getChildNodes();
      for (int i=0; i<ls.getLength(); i++) {
        removeAll(ls.item(i), nodeType, nodeName);
      }
    }
  }

  /**
   * addPI
   * Add a PI at the beginning of the document
   * @param nodeName      The name of the mother node
   * @param ipName        The name of the IP node
   * @param instruct      The instruction to be added
   */
  public void addPI(String nodeName, String ipName, String instruct) {
    NodeList ls = doc.getElementsByTagName(nodeName);
    if (ls!=null && ls.getLength()>0) {
      Element element = (Element)ls.item(0);
      ProcessingInstruction pi = doc.createProcessingInstruction(ipName, instruct);
      element.getParentNode().insertBefore(pi, element);
    } else {
      // Node with the tag name can't be found
      log.error("addPI: node name cannot be found, node name = "+nodeName);
    }
  }

  /**
   * addCDATA
   * Add a CDATA section to the root element
   * @param nodeName      The name of the mother node
   * @param cData         The value of the CDATA to be added
   */
  public void addCDATA(String nodeName, String cData) {
    NodeList ls = doc.getElementsByTagName(nodeName);
    if (ls!=null && ls.getLength()>0) {
      Element element = (Element)ls.item(0);
      CDATASection cdata = doc.createCDATASection(cData);
      element.appendChild(cdata);
    } else {
      // Node with the tag name can't be found
      log.error("addCDATA: node name cannot be found, node name = "+nodeName);
    }
  }

  /** Allow an application to register a content event handler. */
  public void setContentHandler(ContentHandler handler) {
    this.handler = handler;
  }

  /** Return the current content handler. */
  public ContentHandler getContentHandler() {
    return this.handler;
  }

  //=============================================
  // IMPLEMENT THESE FOR A ROBUST APP
  //=============================================
  /** Allow an application to register an error event handler. */
  public void setErrorHandler(ErrorHandler handler) {
  }

  /** Return the current error handler. */
  public ErrorHandler getErrorHandler() {
    return null;
  }

  //=============================================
  // IGNORE THESE
  //=============================================
  /** Parse an XML document from a system identifier (URI). */
  public void parse(InputSource source) throws java.io.IOException, SAXException {
  }

  public void parse(String systemId) throws IOException, SAXException {
  }

  /** Return the current DTD handler. */
  public DTDHandler getDTDHandler() {
    return null;
  }

  /** Return the current entity resolver. */
  public EntityResolver getEntityResolver() {
    return null;
  }

  /** Allow an application to register an entity resolver. */
  public void setEntityResolver(EntityResolver resolver) {
  }

  /** Allow an application to register a DTD event handler. */
  public void setDTDHandler(DTDHandler handler) {
  }

  /** Look up the value of a property. */
  public Object getProperty(String name) {
    return null;
  }

  /** Set the value of a property. */
  public void setProperty(String name, Object value) {
  }

  /** Set the state of a feature. */
  public void setFeature(String name, boolean value) {
  }

  /** Look up the value of a feature. */
  public boolean getFeature(String name) {
    return false;
  }

  /************************ main **************************************/
  public static void main (String argv []) throws Exception {
    XMLUtility xmlUtil = new XMLUtility("D:/CVSHome/EIP/Source/web/WEB-INF/eip-license.xml");
    NodeList nl = xmlUtil.getNodeListByTagName("MODULE");
    for (int i = 0; i < nl.getLength(); i++) {
      Element node = (Element)nl.item(i);
      System.out.println(node.getElementsByTagName("MODULE_CODE").item(0).getNodeValue());
//      System.out.println(node.getAttributeNode("MODULE_CODE").getNodeValue());
    }
  }
}